package in.co.codedoc.ioc;

import in.co.codedoc.util.PropertiesUtil;

import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.PropertyConfigurator;
import org.picocontainer.MutablePicoContainer;
import org.picocontainer.PicoBuilder;

public class IOCContainer
{
	public static void SetApplicationName(String applicationName)
	{
		IOCContainer.applicationName = applicationName;

		String lo4jConfigFileName = applicationName + "-log4j.properties";
		
		URL log4jConfigURL = IOCContainer.class.getClassLoader().getResource(lo4jConfigFileName);
		
		if(log4jConfigURL != null)
		{
			//Configure log4j
			PropertyConfigurator.configure(log4jConfigURL.getFile());
		}
	}
	
	public static String GetApplicationName()
	{
		return applicationName != null ? applicationName : "application";
	}
	
	@SuppressWarnings("unchecked")
	public static synchronized <T> T Lookup(Class<T> clazz)
	{
		Set<Class> implementors = implementorsByType.get(clazz);
		
		return 
			implementors != null && !implementors.isEmpty() 
			? pico.getComponent((Class<T>)implementors.iterator().next()) 
			: null;
	}
	
	@SuppressWarnings("unchecked")
	public static<T> Set<T> LookupAll(Class<T> type)
	{
		Set<T> implementors = new HashSet<T>();
		
		if(implementorsByType.containsKey(type))
		{
			for(Class<T> implementation:implementorsByType.get(type))
			{
				implementors.add(pico.getComponent(implementation));
			}
		}
		
		return implementors;
	}
	
	public static synchronized void ScanClasspath()
	{
		pico = BuildPicoContainer();
		PropertiesUtil.ScanClassPath
		(
			"ioc.properties",
			new PropertiesUtil.ForEach()
			{
				@Override
				public void Do(String key, String value)
				{
					try
					{
						Register(Class.forName(key));
					}
					catch (ClassNotFoundException e)
					{
						throw new RuntimeException
						(
							"Failed to load class " + key + ":" + e.getMessage(),
							e
						);
					}
				}
			}
		);
		
		CreateComponents();
	}
	
	@SuppressWarnings("unchecked")
	public static synchronized void Register(Class clazz)
	{
		if(pico == null)
		{
			pico = BuildPicoContainer();
		}
		
		Set<Class> types = ClassUtil.GetTypes(clazz);
		
		for (Iterator iterator = types.iterator(); iterator.hasNext();)
		{
			Class type = (Class) iterator.next();
			Set<Class> implementors = implementorsByType.get(type);
			
			if(implementors == null)
			{
				implementorsByType.put(type, implementors = new HashSet<Class>());
			}
			
			implementors.add(clazz);		
		}
		
		pico.addComponent(clazz);
	}
	
	@SuppressWarnings("unchecked")
	public static synchronized void ReregisterAll()
	{	
		pico = BuildPicoContainer();
		
		Set<Class> implementors = new HashSet<Class>();
		
		for
		(
			Iterator iterator = implementorsByType.values().iterator();
			iterator.hasNext();
		)
		{
			Set<Class> set = (Set<Class>) iterator.next();
		
			implementors.addAll(set);
		}
		
		for (Iterator iterator = implementors.iterator(); iterator.hasNext();)
		{
			Class clazz = (Class) iterator.next();
		
			pico.addComponent(clazz);
		}
	}
	
	public static synchronized void CreateComponents()
	{
		pico.getComponents();
	}
	
	public static synchronized void InitializeComponents()
	{
		Logger.logger.debug("Initializing components ...");
		
		for(Object component:pico.getComponents())
		{
			if(component instanceof Initializable)
			{
				Logger.logger.debug("Initializing " + component.getClass());
				try
				{
					((Initializable)component).Initialize();
				}
				catch(Throwable th)
				{
					Logger.logger.error
					(
						"Exception while initializing " + component.getClass() + ":" + th.getMessage(),
						th
					);
				}
			}
		}

		Logger.logger.debug("Done Initializing components.");
	}
	
	public static synchronized void StartComponents()
	{
		Logger.logger.debug("Starting components ...");
		for(Object component:pico.getComponents())
		{
			if(component instanceof Startable)
			{
				Logger.logger.debug("Starting " + component.getClass());
				try
				{
					((Startable)component).Start();
				}
				catch(Throwable th)
				{
					Logger.logger.error
					(
						"Exception while Starting " + component.getClass()  + ":" + th.getMessage(),
						th
					);
				}
			}
		}
		Logger.logger.debug("Done Starting components.");
	}
	
	public static synchronized void ShutdownComponents()
	{
		Logger.logger.debug("Stopping components ...",new Throwable());
		
		List<Object> components = pico.getComponents();
		Collections.reverse(components);
		for(Object component:components)
		{
			if(component instanceof Stoppable)
			{
				Logger.logger.debug("Stopping " + component.getClass());
				try
				{
					((Stoppable)component).Stop();
				}
				catch(Throwable th)
				{
					Logger.logger.error
					(
						"Exception while stopping " + component.getClass() + ":" + th.getMessage(),
						th
					);
				}
			}
		}

		Logger.logger.debug("Done Stopping components.");
	}
		
	private static MutablePicoContainer BuildPicoContainer()
	{
		return new PicoBuilder().
				withCaching().
				withConstructorInjection().
				withAutomatic().build();
	}
	
	private static MutablePicoContainer pico;
	@SuppressWarnings("unchecked")
	private static Map<Class, Set<Class>> implementorsByType = new HashMap<Class, Set<Class>>();
	private static String applicationName;	
}
